#include "CCSpineLoader.h"
#include "CCAttachment.h"
#include "CCBoneTransform.h"
#include "CCSlotTransform.h"
#include "ccTypes.h"
#include "platform/CCFileUtils.h"
#include "cJSON.h"

NS_CC_BEGIN

void CCSpineLoader::loadCurve(cJSON *frame, CCTransform::Interpolator &interpolator) {
	cJSON *curveValue = cJSON_GetObjectItem(frame, "curve");
	if (curveValue) {
		if (curveValue->type == cJSON_String && !strcmp(curveValue->valuestring, "stepped")) {
			interpolator.type = CCTransform::STEP;
			return;
		}
		else if (curveValue->type == cJSON_Array) {
			interpolator.type = CCTransform::BEZIER;
			interpolator.set(cJSON_GetArrayItem(curveValue, 0)->valuedouble, cJSON_GetArrayItem(curveValue, 1)->valuedouble,
					cJSON_GetArrayItem(curveValue, 2)->valuedouble, cJSON_GetArrayItem(curveValue, 3)->valuedouble);
			return;
		}
	}
	interpolator.type = CCTransform::LINEAR;
}

CCSkeleton *CCSpineLoader::loadSkeletonFromData(const char *data) {
	cJSON *jsonRoot = cJSON_Parse(data);
	cJSON *jsonBones = cJSON_GetObjectItem(jsonRoot, "bones");

	CCSkeleton *skeleton = CCSkeleton::create();

	if (jsonBones) {
		int size = cJSON_GetArraySize(jsonBones);
		for (int i = 0; i < size; i++) {
			// get key and json for this bone
			cJSON *boneItem = cJSON_GetArrayItem(jsonBones, i);
			const char *key = cJSON_GetObjectItem(boneItem, "name")->valuestring;

			// get parent bone for this bone
			cJSON *parentItem = cJSON_GetObjectItem(boneItem, "parent");
			CCBone *parent = parentItem == NULL ? NULL : skeleton->getBone(parentItem->valuestring);

			// create this bone
			CCBone *bone = CCBone::create();

			// add to parent
			if (parent) {
				parent->addChild(bone);
			}

			// set other property
			CCBone::State &state = bone->getOriginalState();
			state.x = 0;
			state.y = 0;
			state.rotation = 0;
			state.scaleX = 1;
			state.scaleY = 1;

			bone->setName(key);
			cJSON *attrLength = cJSON_GetObjectItem(boneItem, "length");
			if (attrLength) {
				bone->setLength(static_cast<float>(attrLength->valuedouble));
			}
			cJSON *attrX = cJSON_GetObjectItem(boneItem, "x");
			if (attrX) {
				state.x = static_cast<float>(attrX->valuedouble);
			}
			cJSON *attrY = cJSON_GetObjectItem(boneItem, "y");
			if (attrY) {
				state.y = static_cast<float>(attrY->valuedouble);
			}
			cJSON *attrRotation = cJSON_GetObjectItem(boneItem, "rotation");
			if (attrRotation) {
				state.rotation = static_cast<float>(attrRotation->valuedouble);
			}
			cJSON *attrScaleX = cJSON_GetObjectItem(boneItem, "scaleX");
			if (attrScaleX) {
				state.scaleX = static_cast<float>(attrScaleX->valuedouble);
			}
			cJSON *attrScaleY = cJSON_GetObjectItem(boneItem, "scaleY");
			if (attrScaleY) {
				state.scaleY = static_cast<float>(attrScaleY->valuedouble);
			}

			// add bone
			skeleton->addBone(bone);
		}
	}

	cJSON *jsonSlots = cJSON_GetObjectItem(jsonRoot, "slots");
	if (jsonSlots) {
		int size = cJSON_GetArraySize(jsonSlots);
		for (int i = 0; i < size; i++) {
			cJSON *slotItem = cJSON_GetArrayItem(jsonSlots, i);
			// get key and json for this slot
			const char *key = cJSON_GetObjectItem(slotItem, "name")->valuestring;

			// get bone for this slot
			cJSON *attrBone = cJSON_GetObjectItem(slotItem, "bone");

			// create slot
			CCSlot *slot = CCSlot::create();

			CCBone *bone = skeleton->getBone(attrBone->valuestring);

			bone->addSlot(slot);

			// set name
			slot->setName(key);

			// set color
			CCSlot::State &state = slot->getOriginalState();

			cJSON *attrColor = cJSON_GetObjectItem(slotItem, "color");

			if (attrColor) {
				char *buf = attrColor->valuestring;
				state.color = getColorforStr(buf);
			}

			// set active skin attachment name
			cJSON *attrAttachment = cJSON_GetObjectItem(slotItem, "attachment");
			if (attrAttachment) {
				state.activeAttachmentName = CCSpineGlobal::copy(attrAttachment->valuestring);
			}

			cJSON *attrAdditive = cJSON_GetObjectItem(slotItem, "additive");

			if (attrAdditive) {
				state.additive = true;
			}

			// add slot
			skeleton->addSlot(slot);
		}
	}

	cJSON *jsonSkins = cJSON_GetObjectItem(jsonRoot, "skins");
	if (jsonSkins) {
		for (cJSON *skinItem = jsonSkins->child; skinItem; skinItem = skinItem->next) {
			// create skin
			CCSkin *skin = CCSkin::create();
			skin->setName(skinItem->string);

			// parse this skin
			int slotSize = cJSON_GetArraySize(skinItem);
			for (int j = 0; j < slotSize; j++) {
				// get slot
				cJSON *slotItem = cJSON_GetArrayItem(skinItem, j);
				CCSlot *slot = skeleton->getSlot(slotItem->string);

				// get json of attachments
				int attSize = cJSON_GetArraySize(slotItem);

				// parse every attachment
				for (int k = 0; k < attSize; k++) {
					// get attachment name
					cJSON *attachmentItem = cJSON_GetArrayItem(slotItem, k);

					// create skin attachment
					CCAttachment *skinAtt = CCAttachment::create();
					skinAtt->setName(attachmentItem->string);
					skinAtt->setPath(cJSON_GetObjectItem(attachmentItem, "name") ? cJSON_GetObjectItem(attachmentItem, "name")->valuestring : attachmentItem->string);

					// set other property
					skinAtt->setX(static_cast<float>(cJSON_GetObjectItem(attachmentItem, "x") ? cJSON_GetObjectItem(attachmentItem, "x")->valuedouble : 0.0f));
					skinAtt->setY(static_cast<float>(cJSON_GetObjectItem(attachmentItem, "y") ? cJSON_GetObjectItem(attachmentItem, "y")->valuedouble : 0.0f));
					skinAtt->setRotation(static_cast<float>(cJSON_GetObjectItem(attachmentItem, "rotation") ? cJSON_GetObjectItem(attachmentItem, "rotation")->valuedouble : 0.0f));
					skinAtt->setScaleX(static_cast<float>(cJSON_GetObjectItem(attachmentItem, "scaleX") ? cJSON_GetObjectItem(attachmentItem, "scaleX")->valuedouble : 1.0f));
					skinAtt->setScaleY(static_cast<float>(cJSON_GetObjectItem(attachmentItem, "scaleY") ? cJSON_GetObjectItem(attachmentItem, "scaleY")->valuedouble : 1.0f));
					// add attachment
					skin->addSlot(slot, skinAtt);
				}
			}

			// add skin to skeletaon
			skeleton->addSkin(skin);
		}
	}

	map<const char *, SpineEvents, CCSpineGlobal::CCStrPredicate> eventsMap;
	cJSON *jsonEvents = cJSON_GetObjectItem(jsonRoot, "events");
	if (jsonEvents) {
		for (cJSON *eventsItem = jsonEvents->child; eventsItem; eventsItem = eventsItem->next) {
			SpineEvents events;

			events.m_int = cJSON_GetObjectItem(eventsItem, "int") ? cJSON_GetObjectItem(eventsItem, "int")->valueint : 0;
			events.m_float = static_cast<float>(cJSON_GetObjectItem(eventsItem, "float") ? cJSON_GetObjectItem(eventsItem, "float")->valuedouble : 0.0f);
			events.m_string = cJSON_GetObjectItem(eventsItem, "string") ? cJSON_GetObjectItem(eventsItem, "string")->valuestring : "";

			eventsMap.insert(make_pair(eventsItem->string, events));
		}
	}

	cJSON *jsonAnimations = cJSON_GetObjectItem(jsonRoot, "animations");
	if (jsonAnimations) {
		for (cJSON *animationItem = jsonAnimations->child; animationItem; animationItem = animationItem->next) {

			const char *animName = animationItem->string;
			CCSkeletalAnimation *anim = loadAnimation(animationItem, skeleton, eventsMap);
			anim->setName(animName);
			skeleton->addAnim(anim);
		}
	}

	cJSON_Delete(jsonRoot);

	return skeleton;
}

CCSkeletalAnimation *CCSpineLoader::loadAnimation(cJSON *object, CCSkeleton *skeleton, map<const char *, SpineEvents, CCSpineGlobal::CCStrPredicate> &eventsMap) {
	CCSkeletalAnimation *anim = CCSkeletalAnimation::create();
	float duration = 0;
	cJSON *jsonBones = cJSON_GetObjectItem(object, "bones");
	if (jsonBones) {
		// iterate every bone
		int size = cJSON_GetArraySize(jsonBones);
		for (int i = 0; i < size; i++) {
			// get bone key and bone transform json object
			cJSON *boneItem = cJSON_GetArrayItem(jsonBones, i);

			// create transform
			CCBoneTransform *bt = CCBoneTransform::create(skeleton->getBone(boneItem->string));

			//printf("%s\n",boneItem->string);

			// get rotation key frames
			cJSON *attrRotate = cJSON_GetObjectItem(boneItem, "rotate");
			if (attrRotate) {
				int count = cJSON_GetArraySize(attrRotate);
				for (int j = 0; j < count; j++) {
					// get time and angle
					cJSON *rotateKeyFrameValue = cJSON_GetArrayItem(attrRotate, j);
					CCBoneTransform::RotationKeyFrame kf;
					kf.time = static_cast<float>(cJSON_GetObjectItem(rotateKeyFrameValue, "time")->valuedouble);
					kf.angle = static_cast<float>(cJSON_GetObjectItem(rotateKeyFrameValue, "angle")->valuedouble);

					// save max time as duration
					duration = max(duration, kf.time);

					// get interpolator
					loadCurve(rotateKeyFrameValue, kf.interpolator);

					// add key frame
					bt->addRotationKeyFrame(kf);
				}
			} // end if(rkfListJO)

			// get translation key frames
			cJSON *attrTranslate = cJSON_GetObjectItem(boneItem, "translate");
			if (attrTranslate) {
				int count = cJSON_GetArraySize(attrTranslate);
				for (int j = 0; j < count; j++) {
					// get time, x and y
					cJSON *translateKeyFrameValue = cJSON_GetArrayItem(attrTranslate, j);
					CCBoneTransform::TranslationKeyFrame kf;
					kf.time = static_cast<float>(cJSON_GetObjectItem(translateKeyFrameValue, "time")->valuedouble);
					kf.x = static_cast<float>(cJSON_GetObjectItem(translateKeyFrameValue, "x")->valuedouble);
					kf.y = static_cast<float>(cJSON_GetObjectItem(translateKeyFrameValue, "y")->valuedouble);

					// save max time as duration
					duration = max(duration, kf.time);

					// get interpolator
					loadCurve(translateKeyFrameValue, kf.interpolator);

					// add key frame
					bt->addTranslationKeyFrame(kf);
				}
			} // end if(tkfListJO)

			// get scale key frames
			cJSON *attrScale = cJSON_GetObjectItem(boneItem, "scale");
			if (attrScale) {
				int count = cJSON_GetArraySize(attrScale);
				for (int j = 0; j < count; j++) {
					// get time, x and y
					cJSON *scaleKeyFrameValue = cJSON_GetArrayItem(attrScale, j);
					CCBoneTransform::ScaleKeyFrame kf;
					kf.time = static_cast<float>(cJSON_GetObjectItem(scaleKeyFrameValue, "time")->valuedouble);
					kf.scaleX = static_cast<float>(cJSON_GetObjectItem(scaleKeyFrameValue, "x")->valuedouble);
					kf.scaleY = static_cast<float>(cJSON_GetObjectItem(scaleKeyFrameValue, "y")->valuedouble);

					// save max time as duration
					duration = max(duration, kf.time);

					// get interpolator
					loadCurve(scaleKeyFrameValue, kf.interpolator);

					// add key frame
					bt->addScaleKeyFrame(kf);
				}
			} // end if(skfListJO)

			// add transform
			anim->addTransform(bt);
		} // end for
	} // end if(bonesJO)

	cJSON *jsonSlots = cJSON_GetObjectItem(object, "slots");
	if (jsonSlots) {
		for(cJSON* slotItem = jsonSlots->child; slotItem; slotItem = slotItem->next) {
			// create transform
			CCSlotTransform *st = CCSlotTransform::create(skeleton->getSlot(slotItem->string));

			// get slot skin key frames
			cJSON *attrAttachment = cJSON_GetObjectItem(slotItem, "attachment");
			if (attrAttachment) {
				int count = cJSON_GetArraySize(attrAttachment);
				for (int j = 0; j < count; j++) {
					// get time and skin name
					cJSON *attachmentKeyFrameValue = cJSON_GetArrayItem(attrAttachment, j);
					CCSlotTransform::SkinKeyFrame kf;
					kf.time = static_cast<float>(cJSON_GetObjectItem(attachmentKeyFrameValue, "time")->valuedouble);
					kf.attachmentName = CCSpineGlobal::copy(cJSON_GetObjectItem(attachmentKeyFrameValue, "name")->valuestring);


					// save max time as duration
					duration = max(duration, kf.time);

					// interpolator for skin transform is always STEP
					kf.interpolator.type = CCTransform::STEP;

					// add key frame
					// addSkinKeyFrame doesn't copy skin name, so no need to release it
					st->addSkinKeyFrame(kf);
				}
			}

			cJSON *attrColor = cJSON_GetObjectItem(slotItem, "color");
			if (attrColor) {
				int count = cJSON_GetArraySize(attrColor);
				for (int j = 0; j < count; j++) {
					// get time and color
					cJSON *colorKeyFrameValue = cJSON_GetArrayItem(attrColor, j);
					CCSlotTransform::ColorKeyFrame kf;
					kf.time = static_cast<float>(cJSON_GetObjectItem(colorKeyFrameValue, "time")->valuedouble);
					cJSON *colorItem = cJSON_GetObjectItem(colorKeyFrameValue, "color");
					if (colorItem) {
						char *buf = colorItem->valuestring;
						kf.color = getColorforStr(buf);
					}
					else {
						kf.color = ccc4(255, 255, 255, 255);
					}

					// save max time as duration
					duration = max(duration, kf.time);

					// interpolator for color transform is always LINEAR
					kf.interpolator.type = CCTransform::LINEAR;


					// add key frame
					st->addColorKeyFrame(kf);
				}
			}

			// add transform
			anim->addTransform(st);
		} // end for
	} // end if(slotsJO)

	cJSON *jsonEvents = cJSON_GetObjectItem(object, "events");
	if (jsonEvents) {
		int size = cJSON_GetArraySize(jsonEvents);
		for (int i = 0; i < size; i++) {
			// get slot key and slot transform json object
			cJSON *eventsItem = cJSON_GetArrayItem(jsonEvents, i);
			CCSkeletalAnimation::EventsKeyFrame kf;
			kf.time = static_cast<float>(cJSON_GetObjectItem(eventsItem, "time")->valuedouble);

			const char *name = cJSON_GetObjectItem(eventsItem, "name")->valuestring;
			kf.name = name;

			cJSON *intItem = cJSON_GetObjectItem(eventsItem, "int");
			if (intItem) {
				kf.m_int = intItem->valueint;
			}
			else {
				kf.m_int = eventsMap.find(name)->second.m_int;
			}

			cJSON *floatItem = cJSON_GetObjectItem(eventsItem, "float");
			if (floatItem) {
				kf.m_float = static_cast<float>(floatItem->valuedouble);
			}
			else {
				kf.m_float = eventsMap.find(name)->second.m_float;
			}

			cJSON *stringItem = cJSON_GetObjectItem(eventsItem, "string");
			if (stringItem) {
				kf.m_string = stringItem->valuestring;
			}
			else {
				kf.m_string = eventsMap.find(name)->second.m_string;
			}

			duration = max(duration, kf.time);

			anim->addEventsKeyFrame(kf);
		}
	}

	// set duration
	anim->m_duration = duration;

	// return
	return anim;
}

ccColor4B CCSpineLoader::getColorforStr(const char *str) {
	static bool flag = true;
	static map<char, int> mp;
	if (flag) {
		flag = false;
		for (int i = 0; i < 10; i++) {
			mp['0' + i] = i;
		}
		for (int i = 10; i < 16; i++) {
			mp['a' + i - 10] = i;
		}
	}
	GLubyte res[4] = {0};
	for (int i = 0; i < 4; i++) {
		int a = mp[str[i << 1]];
		int b = mp[str[i << 1 | 1]];
		res[i] = (a << 4) + b;
	}
	return ccc4(res[0], res[1], res[2], res[3]);
}

CCSkeleton *CCSpineLoader::loadSkeleton(const char *path) {
	unsigned long length = 0;
	char *data = (char *) CCFileUtils::sharedFileUtils()->getFileData(path, "rb", &length);
	if (data) {
		CCSkeleton *skeleton = loadSkeletonFromData(data);
		delete[] data;
		return skeleton;
	}

	return nullptr;
}

NS_CC_END
